# -*- encoding: ISO-8859-5 -*-
'''
Created on 29.04.2013

@author: Thom
'''
import copy
import threading
import sys

from dame.Interfaces import Gamestate
from dame.Interfaces import Move
from dame.Interfaces import Evaluator
#from dame.DameUI import DameUI

req_version = (2,5)
cur_version = sys.version_info
isVersion25 = True

if cur_version >= req_version:
    isVersion25 = False


"""
    Indizes des Boards
    0,0    0,1    0,2    0,3    0,4
    1,0    1,1    1,2    1,3    1,4
    2,0    2,1    2,2    2,3    2,4
    3,0    3,1    3,2    3,3    3,4
    4,0    4,1    4,2    4,3    4,4
    
    0    1    2    3    4
    5    6    7    8    9
    10   11   12   13   14
    15   16   17   18   19
    20   21   22   23   24
"""

"""
***************************************************************************************************
CheckersBoard
***************************************************************************************************
"""

class CheckersBoard(Gamestate):
    _board = []
    
    _white = 1
    
    _black = 2
    
    _playerToMove = _white
    
    _moveHistory = []
    
    _stateHistory = []
    
    """
        fuer hasNextMove() getNextMove()
    """
    _moves = []
    
    _currentMove = -1
    
    def __init__(self):
        self.setStartState()
        pass
    
    def setStartState(self):
        """
        Startzustand des Boards
        1 1 1 1 1
        1 1 1 1 1
        1 1 0 2 2
        2 2 2 2 2
        2 2 2 2 2 
        """        
        # zuerst alles mit 0 initialisieren
        self._board = []
        for i in range(25):
            self._board.insert(i, 0);
        
        # die ersten zwei Zeilen von Spieler 1
        for row in range(2):
            for col in range(5):
                self.addStone(Stone(self._white, [row, col]))
                
        # dann zwei Spieler 1, einer leer und zwei von Spieler 2
        whiteStartCol = 0
        blackStartCol = 3
        for i in range(2):
            self.addStone(Stone(self._white, [2, whiteStartCol]))
            self.addStone(Stone(self._black, [2, blackStartCol]))
            
            whiteStartCol += 1
            blackStartCol += 1
        
        # die letzten zwei Zeilen von Spieler 2
        for row in range(3,5):
            for col in range(5):
                self.addStone(Stone(self._black, [row, col]))
                
    def getAllMoves(self):
        return self.getAllMovesForColor(self._playerToMove)

    def possibleMove(self, move):
        
        possible = False
        
        fromCoord = [move.from_x, move.from_y]
        toCoord = [move.to_x, move.to_y]
#         removeCoord = [move.rem_x, move.rem_y] 
#        Wieso sind diese string auf dem move?
        removeCoord = [int(move.rem_x), int(move.rem_y)] 
        
        if self.isOnBoard(fromCoord) and self.isOnBoard(toCoord) and self.hasStone(fromCoord) and not self.hasStone(toCoord) and move.player == self._playerToMove:
            stone = self.getStone(fromCoord)
            
            if stone._color == move.player:
                possible = stone.isPossibleMove(toCoord, removeCoord, self)
        
        if possible == False:
            pass
            #print("Move not possible from (" + str(move.from_x) + ", " + str(move.from_y) + ") to (" + str(move.to_x) + ", " + str(move.to_y) + ")")
        
        return possible
    
    def hasNextMove(self):
        self.initIterator()
        
        return self._currentMove < len(self._moves)
        
    def getNextMove(self):
        move = None
        
        if self.hasNextMove():    
            move = self._moves[self._currentMove]
            self._currentMove = self._currentMove + 1
    
        return move
    
    def initIterator(self):
        if self._currentMove == -1:
            self._moves = self.getAllMoves()
            self._currentMove = 0
            
    def resetIterator(self):
        self._moves = []
        self._currentMove = -1
        
    def doMove(self, move):
        if self.possibleMove(move):
            
            self._stateHistory.append(copy.deepcopy(self))
            self._moveHistory.append(move)
            
            fromCoord = [move.from_x, move.from_y]
            toCoord = [move.to_x, move.to_y]
            removeCoord = [int(move.rem_x), int(move.rem_y)]
            
            color = self.getStone(fromCoord)._color
            self.addStone(Stone(color, toCoord))
            self.removeStone(fromCoord)
            
            if removeCoord != [-1, -1]:
                self.removeStone(removeCoord)
            
            # aktueller Spieler wechseln
            self._playerToMove = self.getOppositePlayerColor(self._playerToMove)    
            self.resetIterator()
                
    def undoMove(self, move):
                
        # zuerst ueberpruefen ob dieser Zug als letztes gemacht wurde
        if move == self._moveHistory[-1]:
            # so sind wir auch sicher, dass dieser Zug gemacht werden durfte und wir
            # ihn auch rueckgaengig machen duerfen
            fromCoord = [move.from_x, move.from_y]
            toCoord = [move.to_x, move.to_y]
            removeCoord = [int(move.rem_x), int(move.rem_y)]
            
            #daran denken => rueckgaengig machen!
            color = self.getStone(toCoord)._color
            oppositeColor = self.getOppositePlayerColor(color)
            
            self.removeStone(toCoord)
            self.addStone(Stone(color, fromCoord))
            
            if removeCoord != [-1, -1]:
                self.addStone(Stone(oppositeColor, removeCoord))
                
            self._moveHistory = self._moveHistory[:-1]
            self._stateHistory = self._stateHistory[:-1]
        
        
    def getAllChildStates(self):
        moves = self.getAllMoves()
        children = []
        
        for move in moves:
            child = copy.deepcopy(self)
            child.doMove(move)
            children.append(child)
            
        return children   
    
    def hasNextChild(self):
        self.hasNextMove()
    
    def getNextChild(self):
        move = self.getNextMove()
        child = None
        
        if move:
            child = copy.deepcopy(self)
            child.doMove(move)
            
        return child
    
    def getChild(self, move):
        child = copy.deepcopy(self)
        child.doMove(move)
        
        return child
    
    def firstPlayerToMove(self):
        return self._playerToMove == self._white
    
    def secondPlayerToMove(self):
        return not self.firstPlayerToMove()
    
    def isTerminal(self):
        return len(self.getAllMoves()) == 0
    
    def firstPlayerToWin(self):
        hasStones = self.hasStonesOfColor(self._black)
        
        if hasStones:
            hasMoves = len(self.getAllMovesForColor(self._black)) > 0
            
            return not hasMoves
        else:
            return True
        
    def secondPlayerToWin(self):
        hasStones = self.hasStonesOfColor(self._white)
        
        if hasStones:
            hasMoves = len(self.getAllMovesForColor(self._white)) > 0
            
            return not hasMoves
        else:
            return True
        
    def draw(self):
        """
        tbd
        """
        return False
    
    def getStateHistory(self):
        return self._stateHistory
    
    def getMoveHistory(self):
        return self._moveHistory
    
    def getOppositePlayerColor(self, color):
        
        # aktueller Spieler wechseln
        if color == self._black:
            otherPlayer = self._white
        else:
            otherPlayer = self._black
            
        return otherPlayer
    
    def hasStonesOfColor(self, color):
        hasStone = False
        
        for row in range(5):
            for col in range(5):
                
                coord = [row, col]
                if self.hasStone(coord):
                    stone = self.getStone(coord)
                    
                    if stone._color == color:
                        hasStone = True
                
                if hasStone == True:
                    break
                
            if hasStone == True:
                break
                        
        return hasStone
    
    def getNumStonesOfColor(self, color):
        
        numStones = 0
        
        for row in range(5):
            for col in range(5):
                coord = [row, col]
                
                if self.hasStone(coord):
                    stone = self.getStone(coord)
                    
                    if stone._color == color:
                        numStones = numStones + 1
                        
        return numStones
    
    def getAllMovesForColor(self, color):
        moves = []
        
        for row in range(5):
            for col in range(5):
                coord = [row, col]
                if self.hasStone(coord):
                    stone = self.getStone(coord)
                    
                    if stone._color == color:
                        moves.extend(stone.getAllMoves(self))
                        
        return moves
    
    def addStone(self, stone):
        idx = stone.row() * 5 + stone.col()
        self._board[idx] = stone;
        
    def getStone(self, coord):
        idx = coord[0] * 5 + coord[1]
        return self._board[idx]
    
    def removeStone(self, coord):
        idx = coord[0] * 5 + coord[1]
        self._board[idx] = 0
        
    def moveStone(self, fromCoord, toCoord):
        if self.hasStone(fromCoord) and not self.hasStone(toCoord):
            color = self.getStone(fromCoord)._color
            self.removeStone(fromCoord)
            self.addStone(Stone(color, toCoord))
            
    def isOnBoard(self, coord):
        row = coord[0]
        col = coord[1]
        
        rows = range(0, 5)
        cols = range(0, 5)
        
        return row in rows and col in cols
    
    def hasStone(self, coord):
        
        row = coord[0]
        col = coord[1]
        
        if self.isOnBoard(coord):        
            idx = row * 5 + col
            if self._board[idx] == 0:
                return False
            else:
                return True
        else:
            return False
        
    def printOut(self):
        for row in range(5):
            for col in range(5):
                
                coord = [row, col]
                
                if self.hasStone(coord):
                    stone = self.getStone(coord)
                    if isVersion25:
                        print(stone._color, )
                    else:
                        print(stone._color, " ", end=" ")
                else:
                    if isVersion25:
                        print("0", )
                    else:
                        print("0  ", end=" ")
            
            print("")
            
    def __eq__(self, other):
        return self.__dict__ == other.__dict__
    
    def __str__(self):
        pass

"""
***************************************************************************************************
Stone
***************************************************************************************************
"""

class Stone:
    
    _color = None
    # [row, col]
    _coord = []
    
    def __init__(self, color, coord):
        self._coord = coord
        self._color = color
    
    def row(self):
        return self._coord[0]

    def col(self):
        return self._coord[1]
    
    def getNeighbourCoords(self):
        neighbourCoords = []
        
        # zuerst alle drei Zellen oberhalb des Steins
        row = self.row() - 1
        col = self.col() - 1
        
        for i in range(3):
            neighbourCoords.append([row, col + i])
            
        # nun Zelle links und rechts davon
        row = self.row()
        col = self.col()
        
        neighbourCoords.append([row, col - 1])
        neighbourCoords.append([row, col + 1])
        
        # nun alle Zellen unterhalb des Steins
        row = self.row() + 1
        col = self.col() - 1
        
        for i in range(3):
            neighbourCoords.append([row, col + i])
            
        return neighbourCoords
    
    def isPossibleMove(self, toCoord, remCoord, board):
        neighbourCoords = self.getNeighbourCoords()
        possible = False
        
        for coord in neighbourCoords:
            if coord == toCoord and remCoord == [-1, -1]:
                possible = True
                break
            
        if not possible:
            diff = vectorSubtract(toCoord, self._coord)
            removeCoord = vectorMultiplyScalar(diff, 0.5)
            removeCoord = vectorAdding(removeCoord, self._coord)
            
            if board.hasStone(removeCoord):
                stone = board.getStone(removeCoord)
                
                possible = stone._color != self._color and remCoord == removeCoord
                
        return possible
    
    def getAllMoves(self, board):
        
        coordsToCheck = self.getNeighbourCoords()
            
        moves = []
        
        for coord in coordsToCheck:
            if board.isOnBoard(coord):
                if board.hasStone(coord):
                    # an dieser Stelle ist ein Stein auf dem Brett
                    # ueberpruefen ob dieser Stein ein Stein der anderne Farbe ist
                    otherStone = board.getStone(coord)
                    
                    if otherStone._color != self._color:
                        # wenn hinter diesem gegnerischen Stein (in gleicher Richtung) ein freies Feld ist,
                        # koennen wir diesen ueberspringen
                        vect = vectorSubtract(coord, self._coord)
                        hoppingCord = vectorAdding(coord, vect)
                        
                        if board.isOnBoard(hoppingCord) and not board.hasStone(hoppingCord):
                            moves.append(getMoveFromCoord(self._coord, hoppingCord, self._color, coord))
                else:
                    # kein Stein auf dem Brett -> wir koennen dort hin ziehen
                    moves.append(getMoveFromCoord(self._coord, coord, self._color))
        
        return moves

"""
***************************************************************************************************
CheckersEvaluator
***************************************************************************************************
"""

class CheckersEvaluator(Evaluator):
    
    def evaluate(self, board):
        # anzahl eigene zuege - anzahl zuege des gegner
        playerToMove = board._playerToMove
        otherPlayer = board.getOppositePlayerColor(playerToMove)
        
        numMovesCurrentPlayer = len(board.getAllMovesForColor(playerToMove))
        numMovesOtherPlayer = len(board.getAllMovesForColor(otherPlayer))
        
        moveRating = numMovesCurrentPlayer - numMovesOtherPlayer
        
        # anzahl Steine
        numStonesCurrentPlayer = board.getNumStonesOfColor(playerToMove) * 2
        numStonesOtherPlayer = board.getNumStonesOfColor(otherPlayer) * 2
        
        stoneRating = numStonesCurrentPlayer - numStonesOtherPlayer
        
        rating = moveRating + stoneRating
        
        return rating
    
    def exactValue(self, board):
        return self.evaluate(board)
    
    def heuristicValue(self, board):
        return self.evaluate(board)
    
    def simpleHeuristicValue(self, board):
        return self.evaluate(board)
    
    def getMaxValue(self):
        return 100000
    
    def getMinValue(self):
        return -100000
    
"""
***************************************************************************************************
Utility-Methoden
***************************************************************************************************
"""

def vectorSubtract(vec1, vec2):
    """
        return vec1 - vec2
    """
    diff = []
    
    if len(vec1) == len(vec2):
        for i in range(len(vec1)):
            diff.append(vec1[i] - vec2[i])
            
    return diff

def vectorAdding(vec1, vec2):
    """
        return vec1 + vec2
    """
    add = []
    
    if len(vec1) == len(vec2):
        for i in range(len(vec1)):
            add.append(vec1[i] + vec2[i])
            
    return add

def vectorMultiplyScalar(vec, scalar):
    """
        return int(vec1 * scalar) (Komponentenweise)
    """
    mult = []
    
    for i in range(len(vec)):
        mult.append(int(vec[i] * scalar))
        
    return mult
        
def getMoveFromCoord(fromCoord, toCoord, color, removeCoord = [-1, -1]):
    
    return Move(color, fromCoord[0], fromCoord[1], toCoord[0], toCoord[1], removeCoord[0], removeCoord[1])

